﻿using System;

using PostSharp.Aspects;
using BizElements.BusinessLayer;

namespace BizElements.BusinessLayer.Aspects
{
    #region Documentation.
    /// <summary>
    /// Marks a method as remoteable. Depending on application configuration all calls to this method will be remoted and executed in another domain.
    /// </summary>
    /// <remarks><para>During the build process the code, which determines whether the method call should be remoted, is automatically injected and
    /// intercepts the method marked with <b>RemoteableAttribute</b> attribute.</para>
    /// <para>To use this attribute you have to install PostSharp 2 on your development computer and add references to <b>PostSharp.Laos</b> 
    /// and <b>PostSharp.Public</b> assemblies to your project.</para>    
    /// </remarks>
    /// <example>The following example demonstrates how to use the attribute:
    /// <code>
    /// [Remoteable]
    /// public virtual ICodeList FetchCodeList()
    /// {
    ///     return OrmUtil.FetchCodeList(_ConnectionProvider, this.Table.CountryId, this.Table.CountryName, null);
    /// }
    /// </code>
    /// The previous example is functionally identical to the following pattern:
    /// <code>
    /// public ICodeList FetchCodeList()
    /// {
    ///     return RpcHelper.InvokeMethod&lt;ICodeList&gt;(this, DbFetchCodeList);
    /// }
    /// 
    /// protected virtual ICodeList DbFetchCodeList()
    /// {
    ///     return OrmUtil.FetchCodeList(_ConnectionProvider, this.Table.CountryId, this.Table.CountryName, null);
    /// }
    /// </code>
    /// <para><b>RemoteableAttribute</b> and <b>CachedAttribute</b> can be used together. <b>CachedAttribute</b> has higher aspect priority, 
    /// thus it is evaluated before the <b>RemoteableAttribute</b>. The following example demonstrates how the attributes are combined:
    /// <code>
    /// [Remoteable]
    /// [Cached]
    /// public virtual ICodeList FetchCodeList()
    /// {
    ///     return OrmUtil.FetchCodeList(_ConnectionProvider, this.Table.CountryId, this.Table.CountryName, null);
    /// }
    /// </code>
    /// The previous example is functionally identical to the following pattern:
    /// <code>
    /// public ICodeList FetchCodeList()
    /// {
    ///     // Check if requested data is already cached in the current domain to avoid unnecessary remote method call.
    ///     if (GlobalCache.Contains("CountrySearch.FetchCodeList"))
    ///         return GlobalCache.GetData("CountrySearch.FetchCodeList") as ICodeList;
    /// 
    ///     ICodeList codeList = RpcHelper.InvokeMethod&lt;ICodeList&gt;(this, DbFetchCodeList);
    /// 
    ///     // Cache the data fetched from the data source in the current domain. 
    ///     // All data related to countries is placed in the same cache category ("Countries").
    ///     GlobalCache.SetData(/*cacheKey*/ "CountrySearch.FetchCodeList", codeList, /*category*/ "Countries");
    ///     return codeList;
    /// }
    /// 
    /// protected virtual ICodeList DbFetchCodeList()
    /// {
    ///     // Check if requested data is already cached in the remote domain/process to avoid unnecessary acces to database.
    ///     if (GlobalCache.Contains("CountrySearch.FetchCodeList"))
    ///         return GlobalCache.GetData("CountrySearch.FetchCodeList") as ICodeList;
    /// 
    ///     ICodeList codeList = OrmUtil.FetchCodeList(_ConnectionProvider, this.Table.CountryId, this.Table.CountryName, null);
    /// 
    ///     // Cache the data fetched from the data source in the remote domain. 
    ///     // All data related to countries is placed in the same cache category ("Countries").
    ///     GlobalCache.SetData(/*cacheKey*/ "CountrySearch.FetchCodeList", codeList, /*category*/ "Countries");
    ///     return codeList;
    /// }
    /// </code>
    /// </para>
    /// </example>
    #endregion
    [Serializable]
    [AttributeUsage(AttributeTargets.Method)]
    public sealed class RemoteableAttribute : OnMethodBoundaryAspect
    {
        /// <summary>Initializes a new instance of <b>RemoteableAttribute</b> class and sets its <b>AspectPriority</b>.</summary>
        public RemoteableAttribute()
        {
            AspectPriority = 10;
        }

        /// <summary>Determines whether the method call should be remoted. Executed <b>before</b> the body of methods to which this aspect is applied.</summary>
        /// <param name="eventArgs">Event arguments specifying which method is being executed, which are its arguments, and how should the execution continue after the execution of <b>OnEntry</b>.</param>
        public sealed override void OnEntry(MethodExecutionArgs eventArgs)
        {
            IMethodRemoter remoter = RpcHelper.MethodRemoter;
            bool shouldRemote = (remoter != null) && (remoter.ImplementsRemoting);
            if (shouldRemote)
            {
                var method = eventArgs.Method;
                if (method.IsStatic)
                {
                    if (method.IsGenericMethod)
                        eventArgs.ReturnValue = RpcHelper.InvokeGenericStaticMethod(eventArgs.Method.DeclaringType, method.Name, eventArgs.Arguments.ToArray());
                    else
                        eventArgs.ReturnValue = RpcHelper.InvokeStaticMethod(eventArgs.Method.DeclaringType, method.Name, eventArgs.Arguments.ToArray());
                }
                else
                {
                    if (method.IsGenericMethod)
                        eventArgs.ReturnValue = RpcHelper.InvokeGenericMethod(eventArgs.Instance, method.Name, eventArgs.Arguments.ToArray());
                    else
                        eventArgs.ReturnValue = RpcHelper.InvokeMethod(eventArgs.Instance, method.Name, eventArgs.Arguments.ToArray());
                }

                eventArgs.FlowBehavior = FlowBehavior.Return;
            }
        }
    }
}
